1   /*
2    * Copyright (C) 2006 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.escape;
18  
19  import static com.google.common.base.Preconditions.checkNotNull;
20  
21  import com.google.common.annotations.Beta;
22  import com.google.common.annotations.GwtCompatible;
23  
24  import java.util.HashMap;
25  import java.util.Map;
26  
27  /**
28   * Simple helper class to build a "sparse" array of objects based on the indexes that were added to
29   * it. The array will be from 0 to the maximum index given. All non-set indexes will contain null
30   * (so it's not really a sparse array, just a pseudo sparse array). The builder can also return a
31   * CharEscaper based on the generated array.
32   *
33   * @author Sven Mawson
34   * @since 15.0
35   */
36  @Beta
37  @GwtCompatible
38  public final class CharEscaperBuilder {
39    /**
40     * Simple decorator that turns an array of replacement char[]s into a CharEscaper, this results in
41     * a very fast escape method.
42     */
43    private static class CharArrayDecorator extends CharEscaper {
44      private final char[][] replacements;
45      private final int replaceLength;
46  
47      CharArrayDecorator(char[][] replacements) {
48        this.replacements = replacements;
49        this.replaceLength = replacements.length;
50      }
51  
52      /*
53       * Overriding escape method to be slightly faster for this decorator. We test the replacements
54       * array directly, saving a method call.
55       */
56      @Override public String escape(String s) {
57        int slen = s.length();
58        for (int index = 0; index < slen; index++) {
59          char c = s.charAt(index);
60          if (c < replacements.length && replacements[c] != null) {
61            return escapeSlow(s, index);
62          }
63        }
64        return s;
65      }
66  
67      @Override protected char[] escape(char c) {
68        return c < replaceLength ? replacements[c] : null;
69      }
70    }
71  
72    // Replacement mappings.
73    private final Map<Character, String> map;
74  
75    // The highest index we've seen so far.
76    private int max = -1;
77  
78    /**
79     * Construct a new sparse array builder.
80     */
81    public CharEscaperBuilder() {
82      this.map = new HashMap<Character, String>();
83    }
84  
85    /**
86     * Add a new mapping from an index to an object to the escaping.
87     */
88    public CharEscaperBuilder addEscape(char c, String r) {
89      map.put(c, checkNotNull(r));
90      if (c > max) {
91        max = c;
92      }
93      return this;
94    }
95  
96    /**
97     * Add multiple mappings at once for a particular index.
98     */
99    public CharEscaperBuilder addEscapes(char[] cs, String r) {
100     checkNotNull(r);
101     for (char c : cs) {
102       addEscape(c, r);
103     }
104     return this;
105   }
106 
107   /**
108    * Convert this builder into an array of char[]s where the maximum index is the value of the
109    * highest character that has been seen. The array will be sparse in the sense that any unseen
110    * index will default to null.
111    *
112    * @return a "sparse" array that holds the replacement mappings.
113    */
114   public char[][] toArray() {
115     char[][] result = new char[max + 1][];
116     for (Map.Entry<Character, String> entry : map.entrySet()) {
117       result[entry.getKey()] = entry.getValue().toCharArray();
118     }
119     return result;
120   }
121 
122   /**
123    * Convert this builder into a char escaper which is just a decorator around the underlying array
124    * of replacement char[]s.
125    *
126    * @return an escaper that escapes based on the underlying array.
127    */
128   public Escaper toEscaper() {
129     return new CharArrayDecorator(toArray());
130   }
131 }